﻿using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
namespace nRF24L10P
{
    public class nRF24L10P
    {
        SPI spi;
        OutputPort ce;
        InterruptPort irq;
        public delegate void DeviceStatusChangedDelegate(object sender, DeviceStatusChangedEventArgs arg);
        public event DeviceStatusChangedDelegate DeviceStatusChanged;
        private DeviceStatusEnum status;
        public const byte EMPTY_ADDRESS = 0x00;

        private byte[] singleSendBuffer = new byte[1];
        private byte[] singleReceiveBuffer = new byte[1];

        private byte[] sendBuffer = new byte[33];
        //private byte[] receiveBuffer = new byte[33];
        public Register.Registers Registers { get; private set; }
        private object spiLock = new object();
#if MF_FRAMEWORK_VERSION_V4_3
        private bool revertBytes = BitConverter.IsLittleEndian;//chip uses LSByte first, we should revert byte array order on MSByte first CPU
#endif
#if MF_FRAMEWORK_VERSION_V4_2
        private bool revertBytes = true; // Change this to match your CPU architecture
#endif
        public delegate void HandleInterruptDelegate(Register.Register_STATUS status);
        public event HandleInterruptDelegate OnInterrupt;
        private bool checkStatus = true;
        public nRF24L10P(SPI.SPI_module module, Cpu.Pin csPin, Cpu.Pin cePin, Cpu.Pin irqPin)
        {
            spi = new SPI(new SPI.Configuration(csPin, false, 0, 0, false, true, 2000, module));

            ceEnbled = false;
            ce = new OutputPort(cePin, ceEnbled);

            irq = new InterruptPort(irqPin, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            irq.OnInterrupt += irq_OnInterrupt;
            Thread.Sleep(100); //wait for device to startup

            status = DeviceStatusEnum.PowerDown;
            
            Registers = new Register.Registers(this);
            Registers.LoadAll();//bad sample, should not call external method which references current instance inside the class constructor

            Config = new DeviceConfig(this);
            RXPipes = new DataReadPipe[6];
            for (int i = 0; i < 6; i++)
            {
                RXPipes[i] = new DataReadPipe(this, i);
            }
            TXPipe = new DataWritePipe(this);
        }

        void irq_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            Registers.STATUS.Load();  //refresh status register value
            if (OnInterrupt!=null)
            {
                OnInterrupt(Registers.STATUS);
            }

        }

        /// <summary>
        /// write command with address to SPI
        /// </summary>
        /// <param name="command"></param>
        /// <param name="address"></param>
        /// <param name="value">32 bytes max, no exception handling here</param>
        /// <returns></returns>
        public byte[] SpiReadWrite(byte command, byte address, byte[] value, bool autoRevert =true)
        {
            lock (spiLock)
            {
                if (checkStatus && (command == SPICommands.W_REGISTER && !(this.status == DeviceStatusEnum.StandBy || this.status == DeviceStatusEnum.PowerDown)))
                {
                    throw new InvalidOperationException("Writing register should only in Standby or PowerDown mode");
                }

                int length = value.Length;
                sendBuffer[0] = (byte)(command | address);//first byte is command byte
                byte[] result = new byte[length];



                if (value.Length>1 && autoRevert && revertBytes) //chip uses LSByte first, need to revert the payload order. first byte (command byte) is skipped 
                {
                    for (int i = 0; i <value.Length; i++)
                    {
                        sendBuffer[length - i] = value[i];
                    }
                }
                else
                {
                    Array.Copy(value, 0, sendBuffer, 1, length);
                }

                length++;
                byte[] tmp = new byte[length];// add one byte to spi output buffer length, because the send buffer is added one byte for command
                

                spi.WriteRead(sendBuffer, 0, length, tmp, 0, length, 0);

                this.Registers.STATUS.Value[0] = tmp[0]; //the STATUS register value is returned at first byte on each SPI call

                if (value.Length>1 && autoRevert && revertBytes) //chip uses LSByte first, need to revert the result order,first byte (STATUS register) is skipped
                {
                    for (int i = 0; i < result.Length; i++)
                    {
                        result[i] = tmp[length - (i+1)];
                    }
                }
                else
                {
                    Array.Copy(tmp,1, result, 0, length-1);
                }
                return result;
            }

        }

        /// <summary>
        /// write single command to SPI, return value is status register value
        /// </summary>
        /// <param name="command"></param>
        public byte SpiReadWrite(byte command)
        {
            lock (spiLock)
            {
                singleSendBuffer[0] = command;
                spi.WriteRead(singleSendBuffer, singleReceiveBuffer);
                return singleReceiveBuffer[0];
            }
        }

        public FIFOStatusEnum TXFIFOStatus
        {
            get
            {
                Registers.FIFO_STATUS.Load();
                if (Registers.FIFO_STATUS.TX_FULL)
                {
                    return FIFOStatusEnum.Full;
                }
                if (Registers.FIFO_STATUS.TX_EMPTY)
                {
                    return FIFOStatusEnum.Empty;
                }
                return FIFOStatusEnum.InUse;
            }
        }

        public FIFOStatusEnum RXFIFOStatus
        {
            get
            {
                Registers.FIFO_STATUS.Load();
                if (Registers.FIFO_STATUS.RX_FULL)
                {
                    return FIFOStatusEnum.Full;
                }
                if (Registers.FIFO_STATUS.RX_EMPTY)
                {
                    return FIFOStatusEnum.Empty;
                }
                return FIFOStatusEnum.InUse;
            }
        }


        private bool ceEnbled;

        public bool CEEnabled
        {
            get { return ceEnbled; }
            set
            {
                ce.Write(value);
                ceEnbled = value;
            }
        }

        public bool ChannelRPD
        {
            get
            {
                Registers.RPD.Load();
                return Registers.RPD.RPD;
            }
        }

        public void FlushTX()
        {
            SpiReadWrite(SPICommands.FLUSH_TX);
        }

        public void FlushRX()
        {
            SpiReadWrite(SPICommands.FLUSH_RX);
        }


        public DeviceConfig Config { get; private set; }
        public DataReadPipe[] RXPipes { get; private set; }
        public DataWritePipe TXPipe { get; private set; }

        #region Status Property
        public DeviceStatusEnum Status
        {
            get
            {
                return status;
            }
            set
            {
                if (value == status)
                {
                    return;
                }
                DeviceStatusEnum previous = status;
                status = value;
                if (DeviceStatusChanged != null)
                {

                    handleStatus(previous, status);
                    DeviceStatusChanged(this, new DeviceStatusChangedEventArgs(previous, status));
                }
                else
                {
                    handleStatus(previous, status);
                }
            }
        }
        #endregion

        #region Device status change handler
        private void handleStatus(DeviceStatusEnum previous, DeviceStatusEnum current)
        {
            //clean up previous status
            //TODO: implement cleanup code
            switch (previous)
            {
                case DeviceStatusEnum.Undefined:
                    break;
                case DeviceStatusEnum.PowerDown:
                    break;
                case DeviceStatusEnum.StandBy:
                    break;
                case DeviceStatusEnum.TXMode:
                    break;
                case DeviceStatusEnum.RXMode:
                    //Registers.CONFIG.PRIM_RX = false;  //disable RX role
                    //Registers.CONFIG.Save();
                    break;
                default:
                    break;
            }
            //set device to new status
            switch (current)
            {
                case DeviceStatusEnum.Undefined:
                    throw new InvalidOperationException("WTF???");
                case DeviceStatusEnum.PowerDown:
                    if (previous == DeviceStatusEnum.StandBy)
                    {
                        Registers.CONFIG.PWR_UP = false;
                        Registers.CONFIG.Save();
                        break;
                    }
                    throw new InvalidOperationException("Error status change, PowerDown should from StandBy mode only");
                case DeviceStatusEnum.StandBy:
                    if (previous == DeviceStatusEnum.RXMode || previous == DeviceStatusEnum.TXMode)
                    {
                        irq.DisableInterrupt();
                        CEEnabled = false;
                        break;
                    }
                    if (previous == DeviceStatusEnum.PowerDown)
                    {
                        Registers.CONFIG.PWR_UP = true;
                        Registers.CONFIG.Save();
                        Thread.Sleep(2); //wait for device to power up
                        break;
                    }
                    throw new InvalidOperationException("Error status change, StandBy should from PowerDown,TX or RX mode only");
                case DeviceStatusEnum.TXMode:
                    if (previous == DeviceStatusEnum.StandBy)
                    {
                        checkStatus = false;
                        Registers.CONFIG.PRIM_RX = false;
                        Registers.CONFIG.Save();
                        checkStatus = true;

                        irq.EnableInterrupt();
                        CEEnabled = true;
                        Thread.Sleep(1); //wait device to enter TX mode
                        break;
                    }
                    throw new InvalidOperationException("Error status change, RXMode should from Standyby mode only");
                case DeviceStatusEnum.RXMode:
                    if (previous == DeviceStatusEnum.StandBy)
                    {
                        checkStatus = false;
                        Registers.CONFIG.PRIM_RX = true;
                        Registers.CONFIG.Save();
                        checkStatus = false;
                        irq.EnableInterrupt();
                        CEEnabled = true;
                        Thread.Sleep(1); //wait device to enter RX mode
                        break;

                    }
                    throw new InvalidOperationException("Error status change, RXMode should from Standyby mode only");
                default:
                    break;
            }


        }
        #endregion
    }
}
